package common

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math"
	"math/rand"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/google/uuid"
	"golang.org/x/net/publicsuffix"
	"gopkg.in/gomail.v2"

	"gitee.com/wangyexin/my-tools/conf"
	"gitee.com/wangyexin/my-tools/domains/ins"
	"gitee.com/wangyexin/my-tools/domains/outs"
)

var client http.Client

func init() {
	cookieJar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
	if err != nil {
		panic(err)
	}
	client = http.Client{Jar: cookieJar, Timeout: time.Second * 3}
}

func RecoverErr() {

	if err := recover(); err != nil {
		//输出panic信息
		fmt.Println(err)
		//输出堆栈信息
		fmt.Println(string(debug.Stack()))
	}
}

func CheckErr(err error, flag ...bool) outs.Response {
	if err != nil {
		if len(flag) == 0 || !flag[0] {
			Log(err, nil, INFO)
			return outs.FailWithMessage(err.Error())
		} else {
			log.Panic(err)
		}
	}
	return outs.Ok()
}

func CheckErrData[T any](t T, err error, flag ...bool) T {
	if err != nil {
		if len(flag) == 0 || !flag[0] {
			Log(err, nil, INFO)
		} else {
			log.Panic(err)
		}
	}
	return t
}

// 在指定路径下获取或创建文件, isRemove true 代表每次都是生成新文件
func GetFile(filePath string, isRemove ...bool) *os.File {
	var err error

	if len(isRemove) > 0 && isRemove[0] {
		err = os.Remove(filePath)
		CheckErr(err)
	}

	path := filepath.Dir(filePath)

	err = os.MkdirAll(path, os.ModeDir|os.ModePerm)
	CheckErr(err)

	f, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)

	CheckErr(err)

	return f
}

func HashCode(value string) int32 {
	var hash int32 = 0
	length := len(value)
	if length > 0 {
		for i := 0; i < len(value); i++ {
			hash = 31*hash + int32(value[i])
		}
		return hash
	}
	return hash
}

func HashIndex(value string, bit int32) int32 {
	hash := HashCode(value)
	if hash < 0 {
		hash = -hash
	}
	return hash % bit
}

func IsDigit(data string) bool {
	_, err := strconv.ParseFloat(data, 64)
	return err == nil
}

// JSON 字符串转 Map
func ToMap(v any) map[string]any {
	m := make(map[string]any)
	kind := reflect.TypeOf(v).Kind()
	if kind.String() == "string" {
		err := json.Unmarshal([]byte(v.(string)), &m)
		CheckErr(err)
	} else {
		b, err := json.Marshal(v)
		CheckErr(err)
		err = json.Unmarshal(b, &m)
		CheckErr(err)
	}
	return m
}

// JSON
func ToStruct[T any](v any) T {
	m := new(T)
	kind := reflect.TypeOf(v).Kind()
	if kind.String() == "string" {
		err := json.Unmarshal([]byte(v.(string)), m)
		CheckErr(err)
	} else {
		b, err := json.Marshal(v)
		CheckErr(err)
		err = json.Unmarshal(b, m)
		CheckErr(err)
	}
	return *m
}

func ToStructV2[T any](v any, m *T) {
	kind := reflect.TypeOf(v).Kind()
	if kind.String() == "string" {
		err := json.Unmarshal([]byte(v.(string)), m)
		CheckErr(err)
	} else {
		b, err := json.Marshal(v)
		CheckErr(err)
		err = json.Unmarshal(b, m)
		CheckErr(err)
	}
}

func ToJson(v any) string {
	b, err := json.Marshal(v)
	if err != nil {
		return ""
	}
	return string(b)
}

func Post(requestArgs ins.RequestArgs) outs.Response {
	var err error
	host := conf.GetConf("", "host")
	token := conf.GetConf("", "auth_token")
	if requestArgs.Host != "" {
		if strings.Contains(requestArgs.Host, "http") {
			host = requestArgs.Host
		} else {
			schema := "http"
			if requestArgs.SSL {
				schema = "https"
			}
			host = fmt.Sprintf("%s://%s", schema, requestArgs.Host)
		}
	}
	if requestArgs.Token != "" {
		token = requestArgs.Token
	}
	address := fmt.Sprint(fmt.Sprintf("%s/", host), requestArgs.Method)

	if len(requestArgs.Params) > 0 {
		var rq = url.Values{}
		for k, v := range requestArgs.Params {
			rq.Add(k, v.(string))
		}
		if strings.Contains(address, "?") {
			address = fmt.Sprint(address, "&", rq.Encode())
		} else {
			address = fmt.Sprint(address, "?", rq.Encode())
		}
	}

	// fmt.Println("请求地址:\r\n:", address)

	if requestArgs.RequestGraphqlArgs.Query != "" {
		requestArgs.Body = requestArgs.RequestGraphqlArgs
	}

	var body string
	if value, ok := requestArgs.Body.(string); ok {
		body = value
	} else {
		reqParam, err := json.Marshal(requestArgs.Body)
		CheckErr(err)
		body = string(reqParam)
	}
	// fmt.Printf("请求参数:\r\n%s\r\n======================================================================================\r\n", body)
	if body == "" || body == "null" {
		body = "{}"
	}
	req, err := http.NewRequest("POST", address, strings.NewReader(body))
	if res := CheckErr(err); res.Code > outs.SUCCESS {
		return res
	}
	if nil != requestArgs.Headers && len(requestArgs.Headers) > 0 {
		for k, v := range requestArgs.Headers {
			req.Header.Set(k, v)
		}
	}

	if requestArgs.ContentType == "" {
		requestArgs.ContentType = "application/json;charset=UTF-8"
	}
	req.Header.Set("content-type", requestArgs.ContentType)
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("AUTH-TOKEN", token.(string))
	req.Header.Set("Cookie", fmt.Sprintf("AUTH-TOKEN=%s", token.(string)))
	defer req.Body.Close()
	CheckErr(err)
	resp, err := client.Do(req)
	if res := CheckErr(err); res.Code > outs.SUCCESS {
		return res
	}
	defer resp.Body.Close()

	result, err := io.ReadAll(resp.Body)
	if res := CheckErr(err); res.Code != outs.SUCCESS {
		return res
	}
	response := string(result)
	// fmt.Printf("请求结果:\r\n%s\r\n======================================================================================\r\n", response)

	if !strings.Contains(response, `"errors":null`) {
		panic(fmt.Sprintf("请求出错:入参:%s,结果:%s", body, response))
	}
	return outs.OkWithData(response)
}

func Get(requestArgs ins.RequestArgs) outs.Response {
	host := conf.GetConf("", "host")
	token := conf.GetConf("", "auth_token")
	if requestArgs.Host != "" {
		if strings.Contains(requestArgs.Host, "http") {
			host = requestArgs.Host
		} else {
			schema := "http"
			if requestArgs.SSL {
				schema = "https"
			}
			host = fmt.Sprintf("%s://%s", schema, requestArgs.Host)
		}

		if requestArgs.Token != "" {
			token = requestArgs.Token
		}
	}
	address := fmt.Sprint(fmt.Sprintf("%s/", host), requestArgs.Method)

	if len(requestArgs.Params) > 0 {
		var rq = url.Values{}
		for k, v := range requestArgs.Params {
			rq.Add(k, v.(string))
		}
		if strings.Contains(address, "?") {
			address = fmt.Sprint(address, "&", rq.Encode())
		} else {
			address = fmt.Sprint(address, "?", rq.Encode())
		}
	}
	// fmt.Println("请求地址:\r\n:", address)

	req, err := http.NewRequest("GET", address, nil)
	if res := CheckErr(err); res.Code != outs.SUCCESS {
		return res
	}
	if nil != requestArgs.Headers && len(requestArgs.Headers) > 0 {
		for k, v := range requestArgs.Headers {
			req.Header.Add(k, v)
		}
	}
	req.Header.Add("Sec-Fetch-Mode", "cors")
	req.Header.Set("AUTH-TOKEN", token.(string))
	req.Header.Set("Cookie", fmt.Sprintf("AUTH-TOKEN=%s", token))
	resp, err := client.Do(req)
	if res := CheckErr(err); res.Code != outs.SUCCESS {
		return res
	}
	defer resp.Body.Close()

	if requestArgs.DownloadPath != "" {
		file := GetFile(requestArgs.DownloadPath, true)
		n, err := io.Copy(file, req.Body)
		CheckErr(err)
		fileType, err := GetFileContentType(file)
		CheckErr(err)
		Log(fileType, nil, INFO)
		defer file.Close()
		return outs.OkWithData(n)
	} else {
		result, err := io.ReadAll(resp.Body)
		if res := CheckErr(err); res.Code != outs.SUCCESS {
			return res
		}
		return outs.OkWithData(string(result))
	}
}

func Graphql(body string, host string) outs.Response {
	defer func() {
		if err := recover(); err != nil {
			Log(err, nil, INFO)
		}
	}()
	requestArg := ins.RequestArgs{}
	if host != "" {
		requestArg.Host = host
	}
	requestArg.Body = body
	requestArg.Method = "graphql"
	return Post(requestArg)
}

func GraphqlToken(body string, host string, token string) outs.Response {
	requestArg := ins.RequestArgs{}
	if host != "" {
		requestArg.Host = host
	}
	if token != "" {
		requestArg.Token = token
	}
	requestArg.Body = body
	requestArg.Method = "graphql"
	return Post(requestArg)
}

func GetFileContentType(out *os.File) (string, error) {

	// 只需要前 512 个字节就可以了
	buffer := make([]byte, 512)

	_, err := out.Read(buffer)
	if err != nil {
		return "", err
	}

	contentType := http.DetectContentType(buffer)

	return contentType, nil
}

func Find(slice []string, val string) (int, bool) {
	for i, item := range slice {
		if item == val {
			return i, true
		}
	}
	return -1, false
}

var defaultLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

func GetUUIDNum(n int, allowedChars string) string {
	var letters []rune
	if len(allowedChars) == 0 {
		letters = defaultLetters
	} else {
		letters = ([]rune)(allowedChars)
	}
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}

func GetUUID() string {
	return uuid.New().String()
}

func SendEmail() {
	d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}

func GraphqlJsonToStruct[T any](jsonStr string) outs.Root[T] {
	var root outs.Root[T]
	err := json.Unmarshal([]byte(jsonStr), &root)
	if CheckErr(err).Code == outs.ERROR {
		fmt.Println("json序列化错误：", jsonStr)
	}
	return root
}

func EarthDistance(lng1, lat1, lng2, lat2 float64) float64 {
	radius := 6371000.0 // 6378137
	rad := math.Pi / 180.0

	lat1 = lat1 * rad
	lng1 = lng1 * rad
	lat2 = lat2 * rad
	lng2 = lng2 * rad

	theta := lng2 - lng1
	dist := math.Acos(math.Sin(lat1)*math.Sin(lat2) + math.Cos(lat1)*math.Cos(lat2)*math.Cos(theta))

	return dist * radius
}
